import copy
import time
import abc
from typing import Union, Optional

from selenium.webdriver.remote.webelement import WebElement

import settings
from common.core.globals import p
from common.core.meta import Xpath
from common.core.wait import exceptions as exc
from common.pkg.log import INFO

# 最大等待时间
WAIT_TIME_OUT = 10
# 操作间隔时间
OPERATION_DURATION = 0.5


class Element(WebElement):
    def __init__(self, origin_element: WebElement):
        super().__init__(origin_element.parent, origin_element.id)
        self.name = None


class BaseWait(metaclass=abc.ABCMeta):
    def __init__(self, wait_time_out: Optional[int] = None, operation_duration: Optional[Union[int, float]] = None):
        self.wait_time_out = wait_time_out or WAIT_TIME_OUT
        self.operation_duration = operation_duration or OPERATION_DURATION
        self.error_detail = None

    def __getattribute__(self, item):
        method = object.__getattribute__(self, item)
        if item != "wait":
            if item in ("wait_start", "wait_end"):
                if p.debug or settings.DEBUG:
                    return method
                return lambda: None
            return method

        # 循环执行完成等待的方法
        def _wait(*args, **kwargs):
            for tmp in range(int(self.wait_time_out / self.operation_duration)):
                self.wait_start()
                try:
                    if res := method(*args, **kwargs):
                        self.wait_end()
                        return res
                except Exception as error_detail:
                    self.error_detail = error_detail
                else:
                    self.wait_end()
                    return True
                time.sleep(self.operation_duration)

            else:
                self.wait_exception()

        return _wait

    @abc.abstractmethod
    def wait(self):
        ...

    @abc.abstractmethod
    def wait_start(self):
        ...

    @abc.abstractmethod
    def wait_end(self):
        ...

    @abc.abstractmethod
    def wait_exception(self):
        ...


# 等待单一元素
class SingleElementLocated(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.locator: Xpath = p.get_locator(locator)
        assert not self.locator.many, f"定位单一元素，{self.locator.name!r} 不能指定 many=True"

    def wait(self) -> Element:
        element = Element(p.webdriver.find_element(*self.locator.locator))  # raising error if no found
        # 给定位到的元素绑定名称
        element.name = self.locator.name
        return element

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.locator.name!r} 元素")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.locator.name!r} 成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.locator.name!r} 失败")
        raise exc.NoSuchElementException(f"等待 {self.locator.name!r} 超时，详情=> {self.error_detail!r}")


# 等待某个元素展示到页面中
class SingleElementDisplayed(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.element = SingleElementLocated(locator).wait()

    def wait(self) -> Element:
        assert self.element.is_displayed() is True
        return self.element

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至可见")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至可见成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.element.name!r} 元素至可见失败")
        raise exc.ElementNeverDisplayException(f"元素 {self.element.name!r} 超时不可见")


# 等待某个元素可操作
class SingleElementEnabled(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.element = SingleElementDisplayed(locator).wait()

    def wait(self) -> Element:
        assert self.element.is_enabled()
        return self.element

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至可操作")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至可操作成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.element.name!r} 元素至操作失败")
        raise exc.ElementNeverEnabledException(f"元素 {self.element.name!r} 超时不可操作")


# 等待多个元素
class MultiElementsLocated(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.locator = p.get_locator(locator)
        assert self.locator.many, f"定位一组元素，{self.locator.name!r} 必须指定 many=True"

    def wait(self) -> list[Element]:
        if not (elements := p.webdriver.find_elements(*self.locator.locator)):
            raise exc.NoSuchElementException
        els = []
        for element in elements:
            # 给定位到的元素绑定名称
            element = Element(element)
            element.name = self.locator.name
            els.append(element)
        return els

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.locator.name!r} 元素组")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.locator.name!r} 元素组成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.locator.name!r} 元素组失败")
        raise exc.NoSuchElementException(f"等待 {self.locator.name!r} 元素组超时，详情=> {self.error_detail!r}")


# 等待一组元素展示到页面中
class MultiElementsDisplayed(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.elements = MultiElementsLocated(locator).wait()

    def wait(self) -> list[Element]:
        assert all([ele.is_displayed() for ele in self.elements])
        return self.elements

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.elements[0].name!r} 元素组至可见")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.elements[0].name!r} 元素组至可见成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.elements[0].name!r} 元素组至可见失败")
        raise exc.ElementNeverDisplayException(f"组元素 {self.elements[0].name!r} 超时不可见")


# 等待一组元素可操作
class MultiElementsEnabled(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.elements = MultiElementsDisplayed(locator).wait()

    def wait(self) -> list[Element]:
        assert all([ele.is_enabled() for ele in self.elements])
        return self.elements

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.elements[0].name!r} 元素组至可操作")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.elements[0].name!r} 元素组至可操作成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.elements[0].name!r} 元素组至操作失败")
        raise exc.ElementNeverDisplayException(f"组元素 {self.elements[0].name!r} 超时不可见")


# 等待元素静止后（页面动画过渡之类）再操作
class ElementStopMoving(BaseWait):
    def __init__(self, locator: Union[Xpath, str], **kwargs):
        super().__init__(**kwargs)
        self.element = SingleElementDisplayed(locator).wait()
        self.location = copy.deepcopy(self.element.location)
        self._count = 0

    def wait(self) -> None:
        new_location = self.element.location
        try:
            assert new_location == self.element.location
            self._count += 1
            assert self._count == 3
        except AssertionError:
            self.location = new_location
            raise

    def wait_start(self) -> None:
        INFO.info(f"等待 {self.element.name!r}元素至停止移动")

    def wait_end(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至停止移动成功")

    def wait_exception(self) -> None:
        INFO.info(f"等待 {self.element.name!r} 元素至停止移动失败")
        raise exc.ElementNeverStopMovingException(f"元素 {self.element.name!r} 长时间处于移动状态")


# 当前页面 dom 是否加载完成
class DomLoadComplete(BaseWait):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def wait(self) -> None:
        assert p.webdriver.execute_script("return document.readyState") == "complete"

    def wait_start(self) -> None:
        INFO.info("等待 dom 至加载完成")

    def wait_end(self) -> None:
        INFO.info("等待 dom 至加载完成成功")

    def wait_exception(self) -> None:
        INFO.error("等待 dom 至加载完成失败")
        raise exc.DomNeverLoadedException(f"加载dom超时或错误，详情 => {self.error_detail}")


# 等待路径成功跳转
class UrlContains(BaseWait):
    def __init__(self, url: str, **kwargs):
        super().__init__(**kwargs)
        self.url = url

    def wait(self) -> None:
        assert self.url in p.webdriver.current_url

    def wait_start(self) -> None:
        INFO.info(f"等待URL至包含 {self.url!r}")

    def wait_end(self) -> None:
        INFO.info(f"等待URL至包含 {self.url!r} 成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待URL至包含 {self.url!r} 失败")
        raise exc.URLNeverContains(f"url: {self.url!r} 页面长时间未正确跳转或匹配")


# 等待某个请求完成
class RequestComplete(BaseWait):
    def __init__(self, path: str, **kwargs):
        super().__init__(**kwargs)
        self.path = path

    def wait(self) -> None:
        for request in p.webdriver.requests:
            if request.response:
                if self.path in str(request.url):
                    assert str(request.response.status_code).startswith("2")

    def wait_start(self) -> None:
        INFO.info(f"等待请求 {self.path!r}至完成")

    def wait_end(self) -> None:
        INFO.info(f"等待请求 {self.path!r} 至完成成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待请求 {self.path!r} 至完成失败")
        raise exc.RequestNeverComplete(f"path: {self.path!r} 请求长时间未完成或请求失败")


# 文本出现在 dom 中
class TextAppearInDom(BaseWait):
    def __init__(self, text: str, **kwargs):
        super().__init__(**kwargs)
        self.text = text

    def wait(self):
        assert self.text in p.webdriver.page_source

    def wait_start(self):
        INFO.info(f"等待 {self.text!r} 出现在 dom 中")

    def wait_end(self):
        INFO.info(f"等待 {self.text!r} 出现在 dom 中成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.text!r} 出现在 dom 中失败")
        raise exc.TextNotAppearInDom(f"{self.text!r} 长时间未出现在 dom 中")


# 文本从 dom 中消失
class TextDisappearFromDom(TextAppearInDom):
    def wait(self):
        assert self.text not in p.webdriver.page_source

    def wait_start(self):
        INFO.info(f"等待 {self.text!r} 从 dom 中消失")

    def wait_end(self):
        INFO.info(f"等待 {self.text!r} 从 dom 中消失成功")

    def wait_exception(self) -> None:
        INFO.error(f"等待 {self.text!r} 从 dom 中消失失败")
        raise exc.TextNotAppearInDom(f"{self.text!r} 长时间未从 dom 中消失")
